use crate::imp::tonicrpc::tonicserverimp::FileMetaInfo;
use openssl::sha;
use std::{
    path::PathBuf,
    sync::RwLock,
    time::{Duration, SystemTime, UNIX_EPOCH},
};
use tokio::{
    fs::{remove_file, rename, File, OpenOptions},
    io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader, BufWriter},
    time::Instant,
};
use tonic_inf::api::{FileCheck, FileMeta};

pub const FILE_BUFFER_SIZE: usize = 4 * 1024;

// file match sha256
pub async fn file_match_sha256(
    file_path: &PathBuf,
    file_checks: Vec<FileCheck>,
    need_sha256: bool,
) -> anyhow::Result<(FileMetaInfo, Vec<(bool, u64)>, sha::Sha256)> {
    log::info!(
        "file_match_sha256 file_path:{:?} file_checks: {:?}",
        file_path,
        file_checks
    );
    // read file attributes
    let attr = tokio::fs::metadata(file_path).await?;
    let path = file_path;

    // read file name
    let file_name = path
        .file_name()
        .unwrap_or_default()
        .to_str()
        .unwrap_or_default()
        .to_owned();

    // read file size
    let file_size = attr.len();
    // read file creation time
    let creation_time = match attr.created() {
        Ok(time) => time
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as u64,
        Err(e) => {
            log::warn!(
                "file_match_sha256 send_file {:?} created time get err:{}",
                path.to_str(),
                &e
            );
            0
        }
    };

    // read file last modification time
    let last_modification_time = match attr.modified() {
        Ok(time) => time
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as u64,
        Err(e) => {
            log::warn!("send_file {:?} modified time get err:{}", path.to_str(), &e);
            0
        }
    };

    // read file mime type
    let mime = mime_guess::from_path(file_path)
        .first_or_octet_stream()
        .to_string();
    let is_file = attr.is_file();

    // if no need sha256,then returm file metainfo.
    if !need_sha256 {
        return Ok((
            FileMetaInfo {
                file_name,
                file_size,
                file_sha256: vec![],
                creation_time,
                last_modification_time,
                mime,
                is_file,
            },
            vec![],
            sha::Sha256::new(),
        ));
    };

    // calculate file sha256,
    // tuple( need_calc_sha256,_calc_sha256_size, calc_sha256_left,file_sha256,is_match)

    let mut calc_sha256s: Vec<(bool, u64, i64, Vec<u8>, bool)> = file_checks
        .into_iter()
        .map(|file_check| {
            let mut calc_sha256_left = 0;
            let mut need_calc_sha256 = false;
            let mut _calc_sha256_size = 0;
            let is_match = false;
            let FileCheck {
                file_size: calc_sha256_size,
                file_sha256,
            } = file_check;
            if calc_sha256_size < file_size as u64 && calc_sha256_size > 0 {
                calc_sha256_left = calc_sha256_size as i64;
                need_calc_sha256 = true;
                _calc_sha256_size = calc_sha256_size;
            } else if calc_sha256_size == file_size {
                _calc_sha256_size = file_size;
            }
            (
                need_calc_sha256,
                _calc_sha256_size,
                calc_sha256_left,
                file_sha256,
                is_match,
            )
        })
        .collect();

    log::info!("file_match_sha256 calc_sha256s: {:?}", calc_sha256s);

    let mut bytes_left = file_size as usize;
    let mut ctx = sha::Sha256::new();
    let file = File::open(file_path).await?;
    let mut buf = [0u8; FILE_BUFFER_SIZE];
    let mut read_buffer = BufReader::new(file);
    while bytes_left > 0 {
        let buffer_size = if bytes_left > FILE_BUFFER_SIZE {
            FILE_BUFFER_SIZE
        } else {
            bytes_left
        };
        let size = read_buffer.read(&mut buf[..buffer_size]).await?;
        if size == 0 {
            break;
        }
        bytes_left = bytes_left - size;
        for (need_calc_sha256, _calc_sha256_size, calc_sha256_left, file_sha256, is_match) in
            calc_sha256s.iter_mut()
        {
            if *need_calc_sha256 {
                let left_size = *calc_sha256_left;
                let size = size as i64;

                let final_size = if left_size > size {
                    *calc_sha256_left = left_size - size;
                    -1
                } else {
                    left_size
                };
                if final_size >= 0 {
                    *need_calc_sha256 = false;
                    let mut context = ctx.clone();
                    if final_size > 0 {
                        context.update(&buf[..final_size as usize]);
                    }
                    let calc_sha256 = context.finish().as_ref().to_vec();
                    *is_match = calc_sha256 == *file_sha256;
                    log::info!(
                        "final_size > 0 is_match:{} calc_sha256: {:?} file_sha256:{:?}",
                        is_match,
                        calc_sha256,
                        file_sha256
                    );
                }
            }
        }
        ctx.update(&buf[..size]);
    }
    let context = ctx.clone();
    let file_sha256 = ctx.finish().as_ref().to_vec();

    for (_need_calc_sha256, calc_sha256_size, _calc_sha256_left, to_match_sha256, is_match) in
        calc_sha256s.iter_mut()
    {
        if *calc_sha256_size == file_size {
            *is_match = *to_match_sha256 == file_sha256;
        }
    }

    // output tuple(file is match , calculate size)
    let outs = calc_sha256s
        .into_iter()
        .map(
            |(_need_calc_sha256, calc_sha256_size, _calc_sha256_left, _file_sha256, is_match)| {
                (is_match, calc_sha256_size)
            },
        )
        .collect();

    Ok((
        FileMetaInfo {
            file_name,
            file_size,
            file_sha256,
            creation_time,
            last_modification_time,
            mime,
            is_file,
        },
        outs,
        context,
    ))
}
